import { HttpError } from "@kubernetes/client-node"
import { ExecuteContext, K8SResourceType, KVString, OperateFunction, PlanContext } from "yam-types"
import { ApplicationSecret } from "../types.d"
import { patchVolumeAndEnv, randomAlphabet, randomAlphabetNumeric, randomHex, randomNumeric } from "./util"

const secretOperator: OperateFunction<ApplicationSecret> = async (plan, diff) => {
  // generate new secret if needed, secret-operator does NOT support key rotation
  if (diff.hasNew || diff.hasModified) {
    const changedSecrets = [...diff.newItems, ...diff.modifiedItems.map(x => x.current)]
    changedSecrets.forEach(item => {
      const secrets = {} as KVString
      item.keys?.forEach(key => {
        if (key.generateRandom) {
          secrets[key.name] = Buffer.from(generateSecretKey(key.generateRandom.length, key.generateRandom.format)).toString('base64')
        }
      })
      if (Object.keys(secrets).length > 0) {
        plan.action(async (ctx) => {
          try {
            await ctx.k8sClient.find({
              kind: K8SResourceType.Secret,
              name: item.name
            })
          } catch (e) {
            // only adding secrets when not found, YAM engine doesn't support secret key rotation
            if (e instanceof HttpError && e.statusCode === 404) {
              await ctx.k8sClient.saveConf({
                secret: true,
                data: secrets,
                name: item.name
              })
              return
            }
          }
          ctx.log.warn(`the secret ${item.name} exists, engine will NOT replace it, if you want to rotate original secret, please take other ways`)
        }, "create-new-secret:" + item.name)
      }
    })
  }
  for (const item of diff.currentItems) {
    const secretName = item.name
    plan.action(async (ctx) => {
      // handle volume/env mount for k8s secrets
      patchVolumeAndEnv(ctx, item, plan, secretName, "secret")

      // inject metadata for 3rd party secret providers like AWS SecretManager, Vault, etc.
      injectSecretProvider(ctx, plan, item)
    }, `sync-configMap:${item.name}`)
  }
}

const injectSecretProvider = (ctx: ExecuteContext, plan: PlanContext, item: ApplicationSecret) => {
  if (!item.kms) {
    return
  }
  switch (item.kms.provider) {
    case "aws":
      // inject roleArn into serviceAccount for AWS EKS, app could fetch secret by calling AWS STS and SecretManager
      // env AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE will be injected by aws/amazon-eks-pod-identity-webhook
      // ref: https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts
      ctx.mergeToYaml({
        name: plan.data.serviceAccountName,
        jsonPath: 'metadata',
        defer: true,
        content: {
          annotations: {
            "eks.amazonaws.com/role-arn": item.kms.iamRole
          }
        }
      })
      break
    case "vault":
      // TODO support vault secret injection
      break
  }
}


type KeyGenerationFormat = "numeric" | "alphabet" | "alphabetNumeric" | "hex"

const generateSecretKey = (length: number, format: KeyGenerationFormat): string => {
  switch (format) {
    case "alphabet":
      return randomAlphabet(length)
    case "numeric":
      return randomNumeric(length)
    case "alphabetNumeric":
      return randomAlphabetNumeric(length)
    case "hex":
      return randomHex(length)
    default:
      throw new Error('key generator format not supported')
  }
}

export default secretOperator